package com.share.service.impl;

import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.json.JSONParser;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.share.constants.CacheConstants;
import com.share.constants.MQConstants;
import com.share.constants.OrderStatus;
import com.share.domain.DistanceResult;
import com.share.domain.RuleStartVo;
import com.share.domain.RuleVo;
import com.share.exception.GlobalException;
import com.share.mapper.OrderBillMapper;
import com.share.openfeign.RuleOpenfeign;
import com.share.pojo.bo.CreateOrderBo;
import com.share.pojo.domain.OrderBill;
import com.share.pojo.domain.RedisDriverSetting;
import com.share.pojo.domain.GeoSearchResult;
import com.share.pojo.domain.Order;
import com.share.mapper.OrderMapper;
import com.share.pojo.domain.vo.OrderVo;
import com.share.result.JSONResult;
import com.share.service.IOrderService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.share.template.TxMapTemplate;
import com.share.utils.AssertUtil;
import com.share.utils.CodeGenerateUtils;
import io.swagger.v3.core.util.Json;
import lombok.extern.slf4j.Slf4j;
import org.apache.rocketmq.client.consumer.DefaultMQPullConsumer;
import org.apache.rocketmq.client.consumer.PullResult;
import org.apache.rocketmq.client.consumer.PullStatus;
import org.apache.rocketmq.client.exception.MQClientException;
import org.apache.rocketmq.client.producer.SendCallback;
import org.apache.rocketmq.client.producer.SendResult;
import org.apache.rocketmq.common.message.MessageExt;
import org.apache.rocketmq.common.message.MessageQueue;
import org.apache.rocketmq.spring.core.RocketMQTemplate;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.geo.*;
import org.springframework.data.redis.connection.RedisGeoCommands;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.util.*;
import java.util.stream.Collectors;

/**
 * <p>
 * 订单表 服务实现类
 * </p>
 *
 * @author zcj
 * @since 2025-04-12
 */
@Service
@Slf4j
public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements IOrderService {

    @Autowired
    private TxMapTemplate txMapTemplate;
    @Autowired
    private RuleOpenfeign ruleOpenfeign;
    @Autowired
    private RedisTemplate<String,Object> redisTemplate;
    @Autowired
    private RocketMQTemplate rocketMQTemplate;

    @Autowired
    private DefaultMQPullConsumer defaultMQPullConsumer;

    @Autowired
    private RedissonClient redissonClient;
    @Autowired
    private OrderBillMapper orderBillMapper;

    @Override
    @Transactional
    public JSONResult createOrder(CreateOrderBo createOrderBo) {
        //第一步  下单之前判断是否已经有订单了
        Order one = super.getOne(new LambdaQueryWrapper<Order>()
                .eq(Order::getCustomerId, StpUtil.getLoginIdAsLong())
                .in(Order::getStatus, Arrays.asList(OrderStatus.zero, OrderStatus.one, OrderStatus.two, OrderStatus.three)));
        if(one!=null){//已经下过订单了
            throw new GlobalException("你还有没有完成的订单");
        }
        //第二步  没有订单  构建订单
        Order order = getOrder(createOrderBo);
        super.save(order);
        //第三步  构建账单信息
        OrderBill orderBill = buildOrderBill(order);
        orderBillMapper.insert(orderBill);

        //找司机
        List<GeoSearchResult> geoSearchResults = searchGeo(CacheConstants.GEO,
                Double.valueOf(createOrderBo.getStartPlaceLongitude()),
                Double.valueOf(createOrderBo.getStartPlaceLatitude()),
                5.0, 10L);

        for (GeoSearchResult geoSearchResult : geoSearchResults) {
            //得到司机的id
            Object member = geoSearchResult.getMember();
            //从缓存里面取司机的配置  用来判断该司机 是否应该收到该订单
            String key = String.format(CacheConstants.KEY_DRIVER_ONLINE, member.toString());
            RedisDriverSetting driverSetting = (RedisDriverSetting)redisTemplate.opsForValue().get(key);
            if(driverSetting.getAutoAccept()
                    && driverSetting.getOrderDistance()>=order.getExpectsMileage().intValue()
                    && driverSetting.getRangeDistance()>= 5){
                //构建司机显示信息
                OrderVo orderVo = buildOrderVo(order,geoSearchResult);

                String tags = MQConstants.TAG_ORDER2DRIVER + driverSetting.getId();
                SendResult sendResult = rocketMQTemplate.syncSend(MQConstants.TOPIC_ORDER2DRIVER + ":" + tags, orderVo);
                log.info("推送消息给司机： {}, 结果：{}",orderVo,sendResult);
            }
        }


        return JSONResult.success(order.getOrderNo());
    }

    private OrderBill buildOrderBill(Order order) {
        OrderBill orderBill = new OrderBill();
        orderBill.setId(IdUtil.createSnowflake(0L,0L).nextId());
        orderBill.setOrderId(order.getId());
        JSONResult jsonResult = ruleOpenfeign.baseFee();
        RuleStartVo vo = JSON.parseObject(jsonResult.getData().toString(), RuleStartVo.class);
        orderBill.setBaseMileage(vo.getBaseMileage());
        orderBill.setBaseMileageAmount(vo.getAmount());
        orderBill.setExceedBaseMileageEveryKmAmount(vo.getExceedEveryKmAmount());

        orderBill.setFreeBaseWaitingMinute(vo.getFreeBaseWaitingMinute());
        orderBill.setExeceedBaseWaitingEveryKmAmount(vo.getExceedEveryMinuteAmount());

        orderBill.setFreeBaseReturnMileage(vo.getFreeBaseReturnMileage());
        orderBill.setExceedBaseReturnEveryKmAmount(vo.getExceedEveryReturnKmAmount());

        orderBill.setOrderNo(order.getOrderNo());
        orderBill.setCreateTime(new Date());
        return orderBill;
    }

    private OrderVo buildOrderVo(Order order,GeoSearchResult geoSearchResult) {
        DistanceResult distanceResult = txMapTemplate.distanceMeasure(TxMapTemplate.DRIVING,
                Double.valueOf(geoSearchResult.getLat()),
                Double.valueOf(geoSearchResult.getLon()),
                Double.valueOf(order.getStartPlaceLatitude()),
                Double.valueOf(order.getStartPlaceLongitude()));
        //计算 司机到起点的距离
        OrderVo orderVo = new OrderVo();
        orderVo.setOrderNo(order.getOrderNo());
        orderVo.setTo(order.getEndPlace());
        orderVo.setFrom(order.getStartPlace());
        orderVo.setExpectsFee(order.getExpectsOrderAmount());
        orderVo.setMileage(order.getExpectsMileage());
        orderVo.setDistance(distanceResult.getDistance().doubleValue());
        orderVo.setFavourFee(order.getFavourAmount());
        return orderVo;
    }

    /**
     * 坐标搜索
     * @param key ： 司机坐标的key
     * @param longitude ：中心点的经度
     * @param latitude ：中心点的维度
     * @param radius ：搜索的半径
     * @param limit ：匹配几个成员
     * @return ：命中的结果
     */
    public List<GeoSearchResult> searchGeo(String key,double longitude, double latitude,double radius, Long limit){
        //得到圆
        Circle circle = new Circle(new Point(longitude,latitude),new Distance(radius, Metrics.KILOMETERS));
        //配置我想要的结果
        RedisGeoCommands
                .GeoRadiusCommandArgs args = RedisGeoCommands
                                            .GeoRadiusCommandArgs
                                            .newGeoRadiusArgs();
        args.includeDistance()
                .includeCoordinates()
                .sortAscending()
                .limit(limit);
        //得到结果
        GeoResults<RedisGeoCommands.GeoLocation<Object>> geoResults = redisTemplate
                                                                        .opsForGeo()
                                                                        .radius(key, circle, args);
        List<GeoResult<RedisGeoCommands.GeoLocation<Object>>> content = geoResults.getContent();
        if(content.size() == 0){
            return null;
        }
        return content.stream().map(result ->
                new GeoSearchResult(
                        result.getDistance().getValue(),
                        result.getDistance().getUnit(),
                        result.getContent().getName(),
                        result.getContent().getPoint().getX(),
                        result.getContent().getPoint().getY()
                )
        ).collect(Collectors.toList());
    }


    @Override
    public JSONResult cancel(String orderNo) {
        Order one = super.getOne(new LambdaQueryWrapper<Order>().eq(Order::getOrderNo, orderNo));
        if(one != null){
            one.setStatus(OrderStatus.nine);
            one.setUpdateTime(new Date());
            super.updateById(one);
        }
        return JSONResult.success();
    }

    @Override
    public JSONResult checkOrder(String orderNo) {
        Order one = super.getOne(new LambdaQueryWrapper<Order>()
                .eq(Order::getOrderNo, orderNo).in(Order::getStatus,
                        Arrays.asList(OrderStatus.one, OrderStatus.two, OrderStatus.three)));
        if(one != null){
            return JSONResult.success(one);
        }
        return null;
    }

    @Override
    public JSONResult inprocess() {
        long loginIdAsLong = StpUtil.getLoginIdAsLong();
        Order one = super.getOne(new LambdaQueryWrapper<Order>().eq(Order::getCustomerId, loginIdAsLong)
                .in(Order::getStatus, Arrays.asList(OrderStatus.zero, OrderStatus.one, OrderStatus.two, OrderStatus.three)));
        if(one != null){
            return JSONResult.success(one);
        }
        return null;
    }

    @Override
    public JSONResult pullMQOrder() {
        //得到司机id
        long driverId = StpUtil.getLoginIdAsLong();
        //为什么要用list来接收？？？？
        List<OrderVo> list = new ArrayList<>();
        //手动拉取消息
        try {
            Set<MessageQueue> messageQueues = defaultMQPullConsumer.fetchSubscribeMessageQueues(MQConstants.TOPIC_ORDER2DRIVER);

            for (MessageQueue messageQueue : messageQueues) {
                //偏移量  记录当前已经消费到那里
                long offset = defaultMQPullConsumer.fetchConsumeOffset(messageQueue, true);
                //得到tag
                String tags = MQConstants.TAG_ORDER2DRIVER + driverId;
                //手动获取到结果
                PullResult pull = defaultMQPullConsumer.pull(messageQueue, tags, offset, 32);
                //判断是否拿到结果 --
                if(pull != null && pull.getPullStatus().equals(PullStatus.FOUND)){
                    List<MessageExt> messageExtList = pull.getMsgFoundList();
                    //判断  队列里面是否有数据
                    if(messageExtList == null || messageExtList.size() == 0)continue;

                    for (MessageExt messageExt : messageExtList) {

                        String message = new String(messageExt.getBody());
                        OrderVo order = JSON.parseObject(message, OrderVo.class);
                        list.add(order);
                    }
                }
                if(pull != null){
                    //重要：修改消息的消费位置，如果位置不后移，消息会一直被消费
                    defaultMQPullConsumer.updateConsumeOffset(messageQueue,pull.getNextBeginOffset());
                }
            }
        } catch (Exception e) {
            throw new GlobalException("订单拉取失败");
        }
        return JSONResult.success(list);
    }

    /**
     * 为什么抢单要加锁
     *    司机端 可以会有多个司机接收到同一条订单
     *    可能会出现后面一个人把前面的覆盖掉
     * 解决方案 加锁
     *    使用synchronized 有弊端 只能给当前这个服务上锁
     *    如果部署多台是不是就没有用了
     *    使用分布式锁
     *       redis + redission
     *    使用redis做分布式锁的问题
     *       nx  和   xx 来做 明白nx和xx作用  nx key不存在才可以添加进行  xx key存在才可以添加进去
     *    利用nx 和 xx 的特性就可以保证redis里面只有一个
     *    有问题? 死锁 （刚上锁成功  在执行业务的时候报错了  导致后面删除key没有执行）
     *    怎么解决死锁？ 给key加过期时间  又会出现新的问题
     *    最终方案使用  使用lua脚本来解决  他可以将多个命令变成一次操作
     *    我的redission已经将lua脚本集成了 我们直接调用方法就可以
     *
     * @param orderNo
     * @return
     */
    @Override
    public  JSONResult  killOrder(String orderNo) {
        RLock lock = redissonClient.getLock(CacheConstants.LOCK_ORDER_KEY + orderNo);
        Order one = null;
        try {
            lock.lock();
            one = super.getOne(new LambdaQueryWrapper<Order>().eq(Order::getOrderNo, orderNo));
            //没有查询到订单
            AssertUtil.isNotNull(one,"未查询到该订单");
            AssertUtil.isTrue(one.getStatus().equals(0),"该订单已被接单");

            one.setStatus(OrderStatus.one);
            one.setAcceptTime(new Date());
            one.setDriverId(StpUtil.getLoginIdAsLong());
            one.setDriverName(StpUtil.getExtra("username").toString());
            one.setDriverPhone(StpUtil.getExtra("phone").toString());
//            one.setDriverPhoto(StpUtil.getExtra("avatar").toString());
            super.updateById(one);
        } finally {
            if(lock.isLocked()){
                lock.unlock();
            }
        }
        return JSONResult.success(one);
    }

    @Override
    public JSONResult detail(String orderNo) {
        Order one = super.getOne(new LambdaQueryWrapper<Order>().eq(Order::getOrderNo, orderNo));
        return JSONResult.success(one);
    }

    @Override
    public JSONResult arrive(String orderNo) {
        Order one = super.getOne(new LambdaQueryWrapper<Order>().eq(Order::getOrderNo, orderNo));
        AssertUtil.isNotNull(one,"该订单不存在");
        if(one.getStatus().equals(OrderStatus.two)){
            throw new GlobalException("该订单已到达");
        }
        //起点
        String startPlaceLongitude = one.getStartPlaceLongitude();
        String startPlaceLatitude = one.getStartPlaceLatitude();
        //司机位置
        long loginIdAsLong = StpUtil.getLoginIdAsLong();
        List<Point> position = redisTemplate.opsForGeo().position(CacheConstants.GEO, loginIdAsLong);
        Point point = null;
        if(position.size() != 0){
            point = position.get(0);
        }
        //计算两个的距离是否在50m之内
        DistanceResult distanceResult = txMapTemplate.distanceMeasure(TxMapTemplate.DRIVING,
                Double.valueOf(startPlaceLatitude),
                Double.valueOf(startPlaceLongitude),
                point.getY(), point.getX());
        if(distanceResult.getDistance() > 50){
            throw new GlobalException("你还没有到达代驾点");
        }
        //修改订单状态
        one.setArriveTime(new Date());
        one.setStatus(OrderStatus.two);
        super.updateById(one);
        return JSONResult.success();
    }

    @Override
    public JSONResult start(String orderNo) {
        Order one = super.getOne(new LambdaQueryWrapper<Order>().eq(Order::getOrderNo, orderNo));
        AssertUtil.isNotNull(one,"该订单不存在");
        if(one.getStatus().equals(OrderStatus.three)){
            throw new GlobalException("该订单正在运行中");
        }
        Date date = new Date();
        one.setStartTime(date);
        one.setStatus(OrderStatus.three);
        super.updateById(one);
        //计算等待费用

        OrderBill orderBill = orderBillMapper.selectOne(new LambdaQueryWrapper<OrderBill>().eq(OrderBill::getOrderNo, orderNo));
        Date arriveTime = one.getArriveTime();
        Long min = ((date.getTime() - arriveTime.getTime())/1000 / 60);
        orderBill.setWaitingMinute(min.intValue());

        //判断是否需要累加等待费用
        Integer freeBaseWaitingMinute = orderBill.getFreeBaseWaitingMinute();
        if(min.intValue() > freeBaseWaitingMinute ){
            BigDecimal multiply = orderBill.getExeceedBaseWaitingEveryKmAmount().multiply(new BigDecimal(min.intValue() - freeBaseWaitingMinute));
            orderBill.setWaitingAmount(multiply);
        }else {
            orderBill.setWaitingAmount(BigDecimal.ZERO);
        }
        orderBillMapper.updateById(orderBill);
        return JSONResult.success();
    }

    @Override
    public JSONResult end(String orderNo) {
        Order one = super.getOne(new LambdaQueryWrapper<Order>().eq(Order::getOrderNo, orderNo));
        AssertUtil.isNotNull(one,"该订单不存在");
        if(one.getStatus().equals(OrderStatus.four)){
            throw new GlobalException("该订单已结束");
        }
        one.setEndTime(new Date());
        one.setStatus(OrderStatus.four);
        //实际路程
        //起点到当前位置
        //司机的当前位置 手机端会一直上传司机的最新位置
        long loginIdAsLong = StpUtil.getLoginIdAsLong();
        List<Point> position = redisTemplate.opsForGeo().position(CacheConstants.GEO, loginIdAsLong);
        Point point = null;
        if(position.size() != 0){
            point = position.get(0);
        }
        //起点位置
        String startPlaceLongitude = one.getStartPlaceLongitude();
        String startPlaceLatitude = one.getStartPlaceLatitude();
        DistanceResult distanceResult = txMapTemplate.distanceMeasure(TxMapTemplate.DRIVING,
                point.getY(), point.getX(),Double.valueOf(startPlaceLatitude),
                Double.valueOf(startPlaceLongitude));
        //实际里程
        Double distance = distanceResult.getDistance(); //备注 ： 我们的司机坐标是没有变化的 我们直接写死
        distance = 20.0;//假数据
        BigDecimal bigDecimal = new BigDecimal(distance);
        one.setRealMileage(bigDecimal);
        one.setReturnMileage(bigDecimal);
        super.updateById(one);

        //重新计算里程费
        //重新修改账单的里程价格

        //去的实际路程
        OrderBill orderBill = orderBillMapper.selectOne(new LambdaQueryWrapper<OrderBill>().eq(OrderBill::getOrderNo, orderNo));
        if(distance>orderBill.getBaseMileage().doubleValue()){
            BigDecimal subtract = bigDecimal.subtract(orderBill.getBaseMileage());
            orderBill.setExceedBaseMileage(subtract);
            BigDecimal multiply = orderBill.getExceedBaseMileageEveryKmAmount().multiply(subtract);
            orderBill.setExceedBaseMileageAmount(multiply);
            BigDecimal add = multiply.add(orderBill.getBaseMileageAmount());
            orderBill.setMileageAmount(add);
        }else {
            orderBill.setExceedBaseMileage(BigDecimal.ZERO);
            orderBill.setExceedBaseMileageAmount(BigDecimal.ZERO);
            orderBill.setMileageAmount(orderBill.getBaseMileageAmount());
        }
        //返程
        if(distance>orderBill.getFreeBaseReturnMileage().doubleValue()){
            BigDecimal subtract = bigDecimal.subtract(orderBill.getFreeBaseReturnMileage());
            orderBill.setExceedFreeBaseReturnMileage(subtract);
            BigDecimal multiply = subtract.multiply(orderBill.getExceedBaseReturnEveryKmAmount());
            orderBill.setReturnAmont(multiply);
        }else {
            orderBill.setExceedFreeBaseReturnMileage(BigDecimal.ZERO);
            orderBill.setReturnAmont(BigDecimal.ZERO);
        }
        orderBillMapper.updateById(orderBill);
        return JSONResult.success();
    }

    private Order getOrder(CreateOrderBo createOrderBo) {
        Order order = new Order();
        order.setId(IdUtil.createSnowflake(0L,0L).nextId());
        order.setOrderNo(CodeGenerateUtils.generateUnionPaySn());
        order.setCustomerId(StpUtil.getLoginIdAsLong());
        order.setStartPlace(createOrderBo.getStartPlace());
        order.setEndPlace(createOrderBo.getEndPlace());
        //计算距离
        DistanceResult distanceResult = txMapTemplate.distanceMeasure(TxMapTemplate.DRIVING,
                Double.valueOf(createOrderBo.getStartPlaceLatitude()),
                Double.valueOf(createOrderBo.getStartPlaceLongitude()),
                Double.valueOf(createOrderBo.getEndPlaceLatitude()),
                Double.valueOf(createOrderBo.getEndPlaceLongitude()));
        order.setExpectsMileage(new BigDecimal(distanceResult.getDistance()));
        //计算金额
        RuleVo ruleVo = new RuleVo();
        ruleVo.setMileage(distanceResult.getDistance());
        JSONResult pricing = ruleOpenfeign.pricing(ruleVo);
        if(!pricing.isSuccess()){
            throw new GlobalException("服务器异常");
        }
        String amount = pricing.getData().toString();
        order.setExpectsOrderAmount(new BigDecimal(amount));

        order.setCreateTime(new Date());
        order.setCarPlate(createOrderBo.getCarPlate());
        order.setCarType(createOrderBo.getCarType());
        order.setStatus(OrderStatus.zero);
        order.setExpectMinutes(distanceResult.getDuration());
        order.setStartPlaceLongitude(createOrderBo.getStartPlaceLongitude());
        order.setStartPlaceLatitude(createOrderBo.getStartPlaceLatitude());
        order.setEndPlaceLongitude(createOrderBo.getEndPlaceLongitude());
        order.setEndPlaceLatiude(createOrderBo.getEndPlaceLatitude());
        order.setCustomerName(StpUtil.getExtra("username").toString());
        order.setCustomerPhone(StpUtil.getExtra("phone").toString());
        order.setCustomerPhoto("");//不管
        return order;
    }
}
